#pragma once

#include <map>
#include <list>

namespace ZeEngine
{
    class XMLNode;

    typedef std::multimap<std::string, XMLNode*>    XMLNodeMap;
    typedef std::map<std::string, std::string>      XMLNodeAttributeMap;
    typedef std::pair<std::string, XMLNode*>        XMLNodeMapPair;
    typedef std::pair<std::string, std::string>     XMLNodeAttributeMapPair;

    typedef std::list<const XMLNode*>               XMLNodeList;

    class XMLNode
    {
        public:
            ~XMLNode();

            XMLNode* AddChild(const std::string& iName);
            void AddAttribute(const std::string& iName, const std::string& iValue);
            void SetValue(const std::string& iValue);
            void SetName(const std::string& iValue);

            void Search(const std::string& iName, XMLNodeList& oList) const;

            const XMLNodeMap& GetChildren() const { return m_Children; }
            const std::string& GetName()    const { return m_Name; }

            template <typename T>
            T GetAttribute(const std::string iName) const
            {
                XMLNodeAttributeMap::const_iterator it = m_Attributes.find(iName);

                if (it != m_Attributes.end())
                {
                    return Utilities::ConvertStringToType<T>(it->second);
                }

                LOG_ERROR("Could not find attribute: " + iName + " in " + m_Name);

                assert(false);

                return T();
            }

            std::string GetValueString() const
            {
                return m_Value;
            }

            template <typename T>
            T GetValue() const
            {
                return Utilities::ConvertStringToType<T>(m_Value);
            }

            template <typename T>
            T GetValue(const std::string& iPath) const
            {
                XMLNodeList list;
                SearchForNodes(m_Name + "." + iPath, list, *this);

                if (list.size() == 1)
                {
                    const XMLNode* node = list.front();
                    if (node)
                    {
                        return node->GetValue<T>();
                    }
                }
                else
                {
                    LOG_WARNING("Could not extract value for: " + iPath);
                }

                return T();
            }

            template <typename T>
            T GetAttribute(const std::string& iPath, const std::string& iAttribute) const
            {
                XMLNodeList list;
                SearchForNodes(m_Name + "." + iPath, list, *this);

                if (list.size() == 1)
                {
                    const XMLNode* node = list.front();
                    if (node)
                    {
                        return node->GetAttribute<T>(iAttribute);
                    }
                }
                else
                {
                    LOG_WARNING("Could not extract attribute for: " + iPath);
                }

                return T();
            }

        private:
            void SearchForNodes(const std::string& iName, XMLNodeList& oList, const XMLNode& iNode) const;

            XMLNodeMap              m_Children;
            XMLNodeAttributeMap     m_Attributes;
            std::string             m_Value;
            std::string             m_Name;
    };
}
